# $Id: muc.tcl 1740 2009-03-20 14:42:32Z sergei $

# Multi-User Chat support (XEP-0045)

###############################################################################

namespace eval muc {
    set winid 0
    custom::defvar options(gen_enter_exit_msgs) 1 \
	[::msgcat::mc "Generate status messages when occupants\
	    enter/exit MUC compatible conference rooms."] \
	-type boolean -group Chat
    custom::defvar options(gen_muc_status_change_msgs) 0 \
	[::msgcat::mc "Generate groupchat messages when occupant\
	    changes his/her status and/or status message."] \
	-type boolean -group Chat
    custom::defvar options(gen_muc_position_change_msgs) 0 \
	[::msgcat::mc "Generate groupchat messages when occupant's\
	    room position (affiliation and/or role) changes."] \
	-type boolean -group Chat
    custom::defvar options(propose_configure) 0 \
	[::msgcat::mc "Propose to configure newly created MUC room.\
		       If set to false then the default room configuration\
		       is automatically accepted."] \
	-type boolean -group Chat
    custom::defvar options(history_maxchars) 0 \
	[::msgcat::mc "Maximum number of characters in the history in MUC\
		       compatible conference rooms."] \
	-type integer -group Chat
    custom::defvar options(history_maxstanzas) 0 \
	[::msgcat::mc "Maximum number of stanzas in the history in MUC\
		       compatible conference rooms."] \
	-type integer -group Chat
    custom::defvar options(request_only_unseen_history) 0 \
	[::msgcat::mc "Request only unseen (which aren't displayed in the\
		       chat window) messages in the history in MUC compatible\
		       conference rooms."] \
	-type boolean -group Chat
    custom::defvar options(report_muc_rooms) 0 \
	[::msgcat::mc "Report the list of current MUC rooms on\
		       disco#items query."] \
	-type boolean -group IQ
}

# MUC affiliations (for translation):
# [::msgcat::mc "owner"]
# [::msgcat::mc "admin"]
# [::msgcat::mc "member"]
# [::msgcat::mc "outcast"]
# [::msgcat::mc "none"]
# MUC roles (for translation):
# [::msgcat::mc "moderator"]
# [::msgcat::mc "participant"]
# [::msgcat::mc "visitor"]
# (yet another "none" omitted)

###############################################################################

set ::NS(muc)       http://jabber.org/protocol/muc
set ::NS(muc#admin) http://jabber.org/protocol/muc#admin
set ::NS(muc#owner) http://jabber.org/protocol/muc#owner
set ::NS(muc#user)  http://jabber.org/protocol/muc#user

set ::NS(muc#rooms)  http://jabber.org/protocol/muc#rooms

###############################################################################

proc muc::add_groupchat_user_menu_items {m xlib jid} {
    set group [::xmpp::jid::stripResource $jid]

    if {![is_compatible $group]} return

    set mm [menu $m.muc -tearoff 0]
    $mm add command -label [::msgcat::mc "Whois"] \
	-command [list muc::whois $xlib $jid]
    $mm add command -label [::msgcat::mc "Kick"] \
	-command [list muc::change_item_param \
		      {role none} down $xlib $jid ""]
    $mm add command -label [::msgcat::mc "Ban"] \
	-command [list muc::change_item_param \
		      {affiliation outcast} down $xlib $jid ""]
    $mm add command -label [::msgcat::mc "Grant Voice"] \
	-command [list muc::change_item_param \
		      {role participant} up $xlib $jid ""]
    $mm add command -label [::msgcat::mc "Revoke Voice"] \
	-command [list muc::change_item_param \
		      {role visitor} down $xlib $jid ""]
    $mm add command -label [::msgcat::mc "Grant Membership"] \
	-command [list muc::change_item_param \
		      {affiliation member} up $xlib $jid ""]
    $mm add command -label [::msgcat::mc "Revoke Membership"] \
	-command [list muc::change_item_param \
		      {affiliation none} down $xlib $jid ""]
    $mm add command -label [::msgcat::mc "Grant Moderator Privileges"] \
	-command [list muc::change_item_param \
		      {role moderator} up $xlib $jid ""]
    $mm add command -label [::msgcat::mc "Revoke Moderator Privileges"] \
	-command [list muc::change_item_param \
		      {role participant} down $xlib $jid ""]
    $mm add command -label [::msgcat::mc "Grant Admin Privileges"] \
	-command [list muc::change_item_param \
		      {affiliation admin} up $xlib $jid ""]
    $mm add command -label [::msgcat::mc "Revoke Admin Privileges"] \
	-command [list muc::change_item_param \
		      {affiliation member} down $xlib $jid ""]
    #$mm add command -label [::msgcat::mc "Grant Owner Privileges"] \
    #    -command [list muc::change_item_param \
    #    	      {affiliation owner} up $xlib $jid ""]
    #$mm add command -label [::msgcat::mc "Revoke Owner Privileges"] \
    #    -command [list muc::change_item_param \
    #		      {affiliation admin} down $xlib $jid ""]

    $m add cascade -label [::msgcat::mc "MUC"] -menu $mm
}

#hook::add roster_create_groupchat_user_menu_hook \
	  muc::add_groupchat_user_menu_items 39

###############################################################################

proc muc::add_muc_menu_items {m xlib group {state disabled}} {
    if {[is_compatible $group]} {
	set state normal
    }

    set chatid [chat::chatid $xlib $group]

    set mm [menu $m.muc -tearoff 0]

    $mm add command -label [::msgcat::mc "Configure room"] \
	-command [list muc::request_config $chatid]
    $mm add command -label [::msgcat::mc "Edit voice list"] \
	-command [list muc::request_list role participant $chatid]
    $mm add command -label [::msgcat::mc "Edit ban list"] \
	-command [list muc::request_list affiliation outcast $chatid]
    $mm add command -label [::msgcat::mc "Edit member list"] \
	-command [list muc::request_list affiliation member $chatid]
    $mm add command -label [::msgcat::mc "Edit moderator list"] \
	-command [list muc::request_list role moderator $chatid]
    $mm add command -label [::msgcat::mc "Edit admin list"] \
	-command [list muc::request_list affiliation admin $chatid]
    $mm add command -label [::msgcat::mc "Edit owner list"] \
	-command [list muc::request_list affiliation owner $chatid]
    $mm add separator
    $mm add command -label [::msgcat::mc "Destroy room"] \
	-command [list muc::request_destruction_dialog $chatid "" ""]

    $m add cascade -label [::msgcat::mc "MUC"] -menu $mm -state $state
}

#hook::add chat_create_conference_menu_hook muc::add_muc_menu_items 37

proc muc::disco_node_menu_setup {m bw tnode data parentdata} {
    lassign $data type xlib jid node
    switch -- $type {
	item -
	item2 {
	    set identities [disco::browser::get_identities $bw $tnode]

	    if {[lempty $identities]} {
		set identities [disco::browser::get_parent_identities $bw $tnode]
	    }

	    set features [disco::browser::get_features $bw $tnode]

	    if {[lempty $features]} {
		set features [disco::browser::get_parent_features $bw $tnode]
	    }

	    # JID with resource is not a room JID
	    if {[::xmpp::jid::stripResource $jid] != $jid} return

	    # A room must have non-empty node
	    if {[::xmpp::jid::node $jid] == ""} return

	    foreach id $identities {
		if {[::xmpp::xml::getAttr $id category] == "conference"} {
		    foreach f $features {
			if {$f == $::NS(muc)} {
			    add_muc_menu_items $m $xlib $jid normal
			    return
			}
		    }
		}
	    }
	}
    }
}

#hook::add disco_node_menu_hook muc::disco_node_menu_setup 60

###############################################################################

proc muc::handle_commands {chatid user body type} {
    if {$type != "groupchat"} return

    set xlib [chat::get_xlib $chatid]
    set group [chat::get_jid $chatid]
    if {[cequal [crange $body 0 5] "/kick "]} {
	set params {role none}
	set dir down
	lassign [parse_nick_reason $body 6] nick reason
    } elseif {[cequal [crange $body 0 4] "/ban "]} {
	set params {affiliation outcast}
	set dir down
	lassign [parse_nick_reason $body 5] nick reason
    } elseif {[cequal [crange $body 0 6] "/unban "]} {
	set jid [parse_nick $body 7]
	unban $xlib $group $jid
	return stop
    } elseif {[cequal [crange $body 0 6] "/whois "]} {
	set nick [parse_nick $body 7]
	whois $xlib $group/$nick
	return stop
    } elseif {[cequal [crange $body 0 6] "/voice "]} {
	set params {role participant}
	set dir up
	lassign [parse_nick_reason $body 7] nick reason
    } elseif {[cequal [crange $body 0 8] "/devoice "]} {
	set params {role visitor}
	set dir down
	lassign [parse_nick_reason $body 9] nick reason
    } elseif {[cequal [crange $body 0 7] "/member "]} {
	set params {affiliation member}
	set dir up
	lassign [parse_nick_reason $body 8] nick reason
    } elseif {[cequal [crange $body 0 9] "/demember "]} {
	set params {affiliation none}
	set dir down
	lassign [parse_nick_reason $body 10] nick reason
    } elseif {[cequal [crange $body 0 10] "/moderator "]} {
	set params {role moderator}
	set dir up
	lassign [parse_nick_reason $body 11] nick reason
    } elseif {[cequal [crange $body 0 12] "/demoderator "]} {
	set params {role participant}
	set dir down
	lassign [parse_nick_reason $body 13] nick reason
    } elseif {[cequal [crange $body 0 6] "/admin "]} {
	set params {affiliation admin}
	set dir up
	lassign [parse_nick_reason $body 7] nick reason
    } elseif {[cequal [crange $body 0 8] "/deadmin "]} {
	set params {affiliation member}
	set dir down
	lassign [parse_nick_reason $body 9] nick reason
    } else {
	return
    }
    
    change_item_param $params $dir $xlib $group/$nick $reason

    return stop
}

hook::add chat_send_message_hook muc::handle_commands 50

proc muc::parse_nick {body n} {
    return [lindex [parse_nick_reason $body $n] 0]
}

proc muc::parse_nick_reason {body n} {
    # Parse nickname and reason
    # first line is a nick, rest are reason
    set nick_reason [crange $body $n end]
    set ne [string first "\n" $nick_reason]
    if {$ne < 0} {
	set nick $nick_reason
	set reason ""
    } else {
	set nick [string range $nick_reason 0 [expr {$ne - 1}]]
	set reason [string range $nick_reason [expr {$ne + 1}] end]
    }
    return [list $nick [string trim $reason]]
}

###############################################################################

proc muc::commands_comps {chatid compsvar wordstart line} {
    set group [chat::get_jid $chatid]
    if {![is_compatible $group]} return

    upvar 0 $compsvar comps

    if {!$wordstart} {
	lappend comps {/whois } {/kick } {/ban } {/unban } \
	    {/voice } {/devoice } \
	    {/member } {/demember } \
	    {/moderator } {/demoderator } \
	    {/admin } {/deadmin }
    }
}

hook::add generate_completions_hook muc::commands_comps

###############################################################################

proc muc::get_real_jid {xlib jid} {
    variable users

    if {[info exists users(jid,$xlib,$jid)] && \
	    $users(jid,$xlib,$jid) != ""} {
	return $users(jid,$xlib,$jid)
    } else {
	return ""
    } 
}

proc muc::whois {xlib user} {
    set group [::xmpp::jid::stripResource $user]
    set chatid [chat::chatid $xlib $group]
    set nick [chat::get_nick $xlib $user groupchat]

    set real_jid [get_real_jid $xlib $user]

    if {$real_jid != ""} {
	chat::add_message $chatid $group info \
	    [::msgcat::mc "whois '%s': %s" $nick $real_jid] {}
    } else {
	chat::add_message $chatid $group error \
	    [::msgcat::mc "whois '%s': no info" $nick] {}
    }
}

###############################################################################

proc muc::compare_roles {role1 role2} {
    set roles {none visitor participant moderator}

    set idx1 [lsearch -exact $roles $role1]
    set idx2 [lsearch -exact $roles $role2]
    expr {$idx1 - $idx2}
}

proc muc::compare_affs {aff1 aff2} {
    set affs {outcast none member admin owner}

    set idx1 [lsearch -exact $affs $aff1]
    set idx2 [lsearch -exact $affs $aff2]
    expr {$idx1 - $idx2}
}

proc muc::change_item_param {params dir xlib user reason} {
    variable users

    set group [::xmpp::jid::stripResource $user]
    set chatid [chat::chatid $xlib $group]
    set nick  [chat::get_nick $xlib $user groupchat]

    lassign $params key val
    if {![catch { set aff $users(affiliation,$xlib,$user) }] && \
	    ![catch { set role $users(role,$xlib,$user) }]} {
	switch -- $key/$dir {
	    affiliation/up {
		if {[compare_affs $aff $val] >= 0} {
		    chat::add_message $chatid $group error \
			"affiliation $val '$nick':\
			 [::msgcat::mc {User already %s} $aff]" {}
		    return
		}
	    }
	    affiliation/down {
		if {[compare_affs $aff $val] <= 0} {
		    chat::add_message $chatid $group error \
			"affiliation $val '$nick':\
			 [::msgcat::mc {User already %s} $aff]" {}
		    return
		}
	    }
	    role/up {
		if {[compare_roles $role $val] >= 0} {
		    chat::add_message $chatid $group error \
			"role $val '$nick':\
			 [::msgcat::mc {User already %s} $role]" {}
		    return
		}
	    }
	    role/down {
		if {[compare_roles $role $val] <= 0} {
		    chat::add_message $chatid $group error \
			"role $val '$nick':\
			 [::msgcat::mc {User already %s} $role]" {}
		    return
		}
	    }
	    default {
		return
	    }
	}
    }

    set itemsubtags {}
    if {$reason != ""} {
	lappend itemsubtags [::xmpp::xml::create reason -cdata $reason]
    }
    set vars [list nick $nick]
    if {$params == {affiliation outcast}} {
	# For unknown reason banning request MUST be based on
	# user's bare JID (which may be not known by admin)
	set real_jid [get_real_jid $xlib $user]
	if {$real_jid != ""} {
	    set vars [list jid [::xmpp::jid::stripResource $real_jid]]
	}
	
    }
    set item [::xmpp::xml::create item \
		  -attrs [concat $vars $params] \
		  -subelements $itemsubtags]

    ::xmpp::sendIQ $xlib set \
	-query [::xmpp::xml::create query \
			-xmlns $::NS(muc#admin) \
			-subelement $item] \
	-to $group \
	-command [list muc::test_error_res "$params '$nick'" $xlib $group]
}

###############################################################################

proc muc::unban {xlib group jid} {
    ::xmpp::sendIQ $xlib get \
	-query [::xmpp::xml::create query \
			-xmlns $::NS(muc#admin) \
			-subelement [::xmpp::xml::create item \
					    -attrs {affiliation outcast}]] \
	-to $group \
	-command [list muc::unban_continue $xlib $group $jid]
}

proc muc::unban_continue {xlib group jid res child} {
    if {$res != "ok"} {
	chat::add_message [chat::chatid $xlib $group] $group error \
	    "affiliation none '$jid': [error_to_string $child]" {}
	return
    }

    ::xmpp::xml::split $child tag xmlns attrs cdata subels

    set jid [::xmpp::jid::stripResource $jid]
    set found 0
    foreach item $subels {
	::xmpp::xml::split $item stag sxmlns sattrs scdata ssubels
	switch -- $stag {
	    item {
		set jid1 [::xmpp::xml::getAttr $sattrs jid]
		if {$jid == $jid1} {
		    set found 1
		    break
		}
	    }
	}
    }

    if {!$found} {
	chat::add_message [chat::chatid $xlib $group] $group error \
	    "affiliation none '$jid': User is not banned" {}
	return
    }

    set item [::xmpp::xml::create item \
		  -attrs [list jid $jid affiliation none]]

    ::xmpp::sendIQ $xlib set \
	-query [::xmpp::xml::create query \
			-xmlns $::NS(muc#admin) \
			-subelement $item] \
	-to $group \
	-command [list muc::test_unban_res $xlib $group $jid]
}

proc muc::test_unban_res {xlib group jid res child} {
    if {$res != "ok"} {
	chat::add_message [chat::chatid $xlib $group] $group error \
	    "affiliation none '$jid': [error_to_string $child]" {}
	return
    }

    chat::add_message [chat::chatid $xlib $group] $group info \
	"affiliation none $jid: User is unbanned" {}
}

###############################################################################

proc muc::request_destruction_dialog {chatid alt reason} {
    set xlib [chat::get_xlib $chatid]
    set group [chat::get_jid $chatid]

    set warning \
	[::msgcat::mc "Conference room %s will be destroyed\
		       permanently.\n\nProceed?" $group]

    set res [MessageDlg .muc_request_destruction -aspect 50000 -icon warning \
			-type user -buttons {yes no} -default 1 \
			-cancel 1 \
			-message $warning]
    if {!$res} {
	request_destruction $chatid $alt $reason
    }
}

proc muc::request_destruction {chatid alt reason} {
    set xlib [chat::get_xlib $chatid]
    set group [chat::get_jid $chatid]

    if {$alt != ""} {
	set vars [list jid $alt]
    } else {
	set vars {}
    }

    ::xmpp::sendIQ $xlib set \
	-query [::xmpp::xml::create query \
		    -xmlns $::NS(muc#owner) \
		    -subelement [::xmpp::xml::create destroy \
					-attrs $vars \
					-subelement [::xmpp::xml::create reason \
							    -cdata $reason]]] \
	-to $group \
	-command [list muc::test_error_res "destroy" $xlib $group]
}

###############################################################################

proc muc::request_list {attr val chatid} {
    set xlib [chat::get_xlib $chatid]
    set group [chat::get_jid $chatid]
    ::xmpp::sendIQ $xlib get \
	-query [::xmpp::xml::create query \
			-xmlns $::NS(muc#admin) \
			-subelement [::xmpp::xml::create item \
					-attrs [list $attr $val]]] \
	-to $group \
	-command [list muc::receive_list $attr $val $chatid]
}


proc muc::receive_list {attr val chatid res child} {
    set xlib [chat::get_xlib $chatid]
    set group [chat::get_jid $chatid]
    if {![string equal $res ok]} {
	chat::add_message $chatid $group error \
	    "$attr $val list: [error_to_string $child]" {}
	return
    }

    ::xmpp::xml::split $child tag xmlns attrs cdata subels

    variable winid

    set w .muc_list$winid
    incr winid

    if {[winfo exists $w]} {
	destroy $w
    }

    Dialog $w -title [::msgcat::mc [format "Edit %s list" $val]] \
        -modal none -separator 1 -anchor e -default 0 -cancel 1 \
        -parent .

    set wf [$w getframe]

    set sw [ScrolledWindow $wf.sw -scrollbar vertical]
    set sf [ScrollableFrame $w.fields -constrainedwidth yes]
    set f [$sf getframe]
    $sw setwidget $sf
    fill_list $sf $f $subels $attr $val
    list_add_item $sf $f $attr $val
 
    $w add -text [::msgcat::mc "Send"] \
	-command [list muc::send_list $chatid $attr $val $w $f]
    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]
    bind $w <Destroy> [list after idle [list muc::list_cleanup $w $f]]

    frame $w.fr
    pack $w.fr -side bottom -in $wf -fill x

    button $w.add -text [::msgcat::mc "Add new item"] \
	-command [list muc::list_add_item $sf $f $attr $val]
    pack $w.add -side right -in $w.fr -padx 1m -pady 1m

    label $w.lall -text [::msgcat::mc "All items:"]
    pack $w.lall -side left -in $w.fr -padx 1m -pady 1m

    switch -- $attr {
	role {
	    ComboBox $w.roleall -text $val \
		-values {moderator participant visitor none} \
		-editable no \
		-width 11 \
		-modifycmd [list muc::change_all_items $f $w.roleall $attr]
	    pack $w.roleall -side left -anchor w -in $w.fr -pady 1m
	}
	affiliation {
	    ComboBox $w.affiliationall -text $val \
		-values {owner admin member none outcast} \
		-editable no \
		-width 7 \
		-modifycmd [list muc::change_all_items $f $w.affiliationall $attr]
	    pack $w.affiliationall -side left -in $w.fr -pady 1m
	}
    }

    pack $sw -side top -expand yes -fill both

    bindscroll $f $sf

    set hf [frame $w.hf]
    pack $hf -side top
    set vf [frame $w.vf]
    pack $vf -side left

    update idletasks
    $hf configure -width [expr {[winfo reqwidth $f] + [winfo pixels $f 1c]}]

    set h [winfo reqheight $f]
    set sh [winfo screenheight $w]
    if {$h > $sh - 200} {
	set h [expr {$sh - 200}]
    }
    $vf configure -height $h

    $w draw
    $sf yview moveto 1.0
}

###############################################################################

proc muc::change_all_items {f combobox attr} {
    variable listdata
    variable origlistdata

    set value [$combobox get]

    for {set i 1} {$i <= $listdata($f,rows)} {incr i} {
	set listdata($f,$attr,$i) $value
    }
}

###############################################################################

proc muc::fill_list {sf f items attr val} {
    variable listdata
    variable origlistdata

    grid columnconfigure $f 0 -weight 1
    grid columnconfigure $f 1 -weight 1
    grid columnconfigure $f 2 -weight 0
    grid columnconfigure $f 3 -weight 2

    label $f.lnick -text [::msgcat::mc "Nick"]
    grid $f.lnick -row 0 -column 0 -sticky we -padx 1m
    bindscroll $f.lnick $sf
    label $f.ljid -text JID
    grid $f.ljid -row 0 -column 1 -sticky we -padx 1m
    bindscroll $f.ljid $sf
    switch -- $attr {
	role {
	    label $f.lrole -text [::msgcat::mc "Role"]
	    grid $f.lrole -row 0 -column 2 -sticky we -padx 1m
	    bindscroll $f.lrole $sf
	}
	affiliation {
	    label $f.laffiliation -text [::msgcat::mc "Affiliation"]
	    grid $f.laffiliation -row 0 -column 2 -sticky we -padx 1m
	    bindscroll $f.laffiliation $sf
	}
    }
    label $f.lreason -text [::msgcat::mc "Reason"]
    grid $f.lreason -row 0 -column 3 -sticky we -padx 1m
    bindscroll $f.lreason $sf

    set row 1

    set items2 {}
    foreach item $items {
	::xmpp::xml::split $item tag xmlns attrs cdata subels
	switch -- $tag {
	    item {
		set nick [::xmpp::xml::getAttr $attrs nick]
		set jid [::xmpp::xml::getAttr $attrs jid]
		set role [::xmpp::xml::getAttr $attrs role]
		set affiliation [::xmpp::xml::getAttr $attrs affiliation]
		set reason ""
		foreach subitem $subels {
		    ::xmpp::xml::split $subitem stag sxmlns sattrs scdata ssubels
		    if {$stag == "reason"} {
			set reason $scdata
		    }
		}
		lappend items2 [list $nick $jid $role $affiliation $reason]
	    }
	}
    }

    foreach item [lsort -dictionary -index 1 $items2] {
	lassign $item listdata($f,nick,$row) listdata($f,jid,$row) \
		      role affiliation listdata($f,reason,$row)

	entry $f.nick$row \
	    -textvariable muc::listdata($f,nick,$row) \
	    -takefocus 0 \
	    -highlightthickness 0 \
	    -width 20
	if {[catch {$f.nick$row configure -state readonly}]} {
	    $f.nick$row configure -state disabled
	}
	grid $f.nick$row -row $row -column 0 -sticky we -padx 1m
	bindscroll $f.nick$row $sf

	entry $f.jid$row \
	    -textvariable muc::listdata($f,jid,$row) \
	    -takefocus 0 \
	    -highlightthickness 0 \
	    -width 30
	if {[catch {$f.jid$row configure -state readonly}]} {
	    $f.jid$row configure -state disabled
	}
	grid $f.jid$row -row $row -column 1 -sticky we -padx 1m
	bindscroll $f.jid$row $sf

	switch -- $attr {
	    role {
		ComboBox $f.role$row -text $role \
		    -values {moderator participant visitor none} \
		    -editable no \
		    -width 11 \
		    -textvariable muc::listdata($f,role,$row)
		grid $f.role$row -row $row -column 2 -sticky we -padx 1m
		bindscroll $f.role$row $sf
	    }
	    affiliation {
		ComboBox $f.affiliation$row -text $affiliation \
		    -values {owner admin member none outcast} \
		    -editable no \
		    -width 7 \
		    -textvariable muc::listdata($f,affiliation,$row)
		grid $f.affiliation$row -row $row -column 2 -sticky we -padx 1m
		bindscroll $f.affiliation$row $sf
	    }
	}

	entry $f.reason$row \
	    -textvariable muc::listdata($f,reason,$row) \
	    -width 40
	grid $f.reason$row -row $row -column 3 -sticky we -padx 1m
	bindscroll $f.reason$row $sf
	
	incr row
    }

    set listdata($f,rows) [incr row -1]
    array set origlistdata [array get listdata ${f}*]
}

###############################################################################

proc muc::list_add_item {sf f attr val} {
    variable listdata

    set row [incr listdata($f,rows)]

    entry $f.nick$row \
	-textvariable muc::listdata($f,nick,$row) \
	-width 20
    grid $f.nick$row -row $row -column 0 -sticky we -padx 1m
    bindscroll $f.nick$row $sf
    
    entry $f.jid$row \
	-textvariable muc::listdata($f,jid,$row) \
	-width 30
    grid $f.jid$row -row $row -column 1 -sticky we -padx 1m
    bindscroll $f.jid$row $sf
    focus $f.jid$row

    switch -- $attr {
	role {
	    ComboBox $f.role$row -text none \
		-values {moderator participant visitor none} \
		-editable no \
		-width 11 \
		-textvariable muc::listdata($f,role,$row)
	    grid $f.role$row -row $row -column 2 -sticky we -padx 1m
	    bindscroll $f.role$row $sf
	}
	affiliation {
	    ComboBox $f.affiliation$row -text none \
		-values {owner admin member none outcast} \
		-editable no \
		-width 7 \
		-textvariable muc::listdata($f,affiliation,$row)
	    grid $f.affiliation$row -row $row -column 2 -sticky we -padx 1m
	    bindscroll $f.affiliation$row $sf
	}
    }

    entry $f.reason$row \
	-textvariable muc::listdata($f,reason,$row) \
	-width 40
    grid $f.reason$row -row $row -column 3 -sticky we -padx 1m
    bindscroll $f.reason$row $sf

    set listdata($f,$attr,$row) $val
}

###############################################################################

proc muc::send_list {chatid attr val w f} {
    variable origlistdata
    variable listdata

    set items {}

    for {set i 1} {$i <= $origlistdata($f,rows)} {incr i} {
	set vars {}
	if {$listdata($f,$attr,$i) != $origlistdata($f,$attr,$i) || \
		$listdata($f,reason,$i) != $origlistdata($f,reason,$i)} {
	    lappend vars $attr $listdata($f,$attr,$i)
	}

	if {$vars != {}} {
	    if {$origlistdata($f,nick,$i) != ""} {
		lappend vars nick $origlistdata($f,nick,$i)
	    }
	    if {$origlistdata($f,jid,$i) != ""} {
		lappend vars jid $origlistdata($f,jid,$i)
	    }
	    set itemsubtags {}
	    set reason $listdata($f,reason,$i)
	    if {$reason != ""} {
	        lappend itemsubtags [::xmpp::xml::create reason \
	        			 -cdata $reason]
	    }
	    lappend items [::xmpp::xml::create item \
			       -attrs $vars \
			       -subelements $itemsubtags]
	}
    }

    for {} {$i <= $listdata($f,rows)} {incr i} {
	set vars1 {}
	set vars2 {}
	if {$listdata($f,$attr,$i) != ""} {
	    lappend vars1 $attr $listdata($f,$attr,$i)
	}
	if {$listdata($f,nick,$i) != ""} {
	    lappend vars2 nick $listdata($f,nick,$i)
	}
	if {$listdata($f,jid,$i) != ""} {
	    lappend vars2 jid $listdata($f,jid,$i)
	}

	if {$vars1 != {} && $vars2 != {}} {
	    set vars [concat $vars2 $vars1]
	    set itemsubtags {}
	    set reason $listdata($f,reason,$i)
	    if {$reason != ""} {
	        lappend itemsubtags [::xmpp::xml::create reason \
	        			 -cdata $reason]
	    }
	    lappend items [::xmpp::xml::create item \
			       -attrs $vars \
			       -subelements $itemsubtags]
	}
    }

    set xlib [chat::get_xlib $chatid]
    set group [chat::get_jid $chatid]

    if {$items != {}} {
	::xmpp::sendIQ $xlib set \
	    -query [::xmpp::xml::create query \
			       -xmlns $::NS(muc#admin) \
			       -subelements $items] \
	    -to $group \
	    -command [list muc::test_error_res \
			   [::msgcat::mc "Sending %s %s list" $attr $val] \
			   $xlib $group]
    }
    destroy $w
}

###############################################################################

proc muc::list_cleanup {w f} {
    variable listdata
    variable origlistdata

    array unset listdata ${f},*
    array unset origlistdata ${f},*
}

proc muc::request_config_dialog {chatid} {
    variable winid

    set w .muc_req_config$winid
    incr winid

    if {[winfo exists $w]} {
	destroy $w
    }

    Dialog $w -title [::msgcat::mc "Room is created"] \
	-modal none -separator 1 -anchor e -default 0 -cancel 1

    set wf [$w getframe]
    message $wf.message -aspect 50000 \
	-text [::msgcat::mc "Room %s is successfully created" \
		    [chat::get_jid $chatid]]

    pack $wf.message -pady 2m

    $w add -text [::msgcat::mc "Configure room"] \
	-command "[list destroy $w]
		  [list [namespace current]::request_config $chatid]"
    $w add -text [::msgcat::mc "Accept default config"] \
	-command "[list destroy $w]
		  [list [namespace current]::request_instant_room $chatid]"

    $w draw
    
}

proc muc::request_instant_room {chatid} {
    ::xmpp::sendIQ [chat::get_xlib $chatid] set \
	-query [::xmpp::xml::create query \
			-xmlns $::NS(muc#owner) \
			-subelement [::xmpp::data::submitForm {}]] \
	-to [chat::get_jid $chatid]
}

proc muc::request_config {chatid} {
    ::xmpp::sendIQ [chat::get_xlib $chatid] get \
	-query [::xmpp::xml::create query \
			-xmlns $::NS(muc#owner)] \
	-to [chat::get_jid $chatid] \
	-command [list muc::receive_config $chatid]
}

proc muc::receive_config {chatid res child} {
    set group [chat::get_jid $chatid]
    if {![string equal $res ok]} {
	chat::add_message $chatid $group error \
	    [::msgcat::mc "Configure form: %s" [error_to_string $child]] \
	    {}
	return
    }

    ::xmpp::xml::split $child tag xmlns attrs cdata subels

    data::draw_window $subels [list muc::send_config $chatid] \
			      [list muc::cancel_config $chatid]
    return
}

###############################################################################

proc muc::send_config {chatid w restags} {
    set xlib [chat::get_xlib $chatid]
    set group [chat::get_jid $chatid]
    ::xmpp::sendIQ $xlib set \
	-query [::xmpp::xml::create query \
			   -xmlns $::NS(muc#owner) \
			   -subelements $restags] \
	-to $group \
	-command [list muc::test_error_res \
		       [::msgcat::mc "Sending configure form"] $xlib $group]
    destroy $w
}

###############################################################################

proc muc::cancel_config {chatid w} {
    set xlib [chat::get_xlib $chatid]
    set group [chat::get_jid $chatid]
    ::xmpp::sendIQ $xlib set \
	-query [::xmpp::xml::create query \
			   -xmlns $::NS(muc#owner) \
			   -subelement [::xmpp::data::cancelForm]] \
	-to $group \
	-command [list muc::test_error_res \
		       [::msgcat::mc "Cancelling configure form"] $xlib $group]
    destroy $w
}

###############################################################################

proc muc::test_error_res {op xlib group res child} {
    if {![string equal $res ok]} {
	set chatid [chat::chatid $xlib $group]
	chat::add_message $chatid $group error \
	    [format "%s: %s" $op [error_to_string $child]] {}
	return
    }
}

###############################################################################

proc muc::process_presence {xlib from type x args} {
    switch -- $type {
	available -
	unavailable {
	    foreach xs $x {
		::xmpp::xml::split $xs tag xmlns attrs cdata subels
		if {$xmlns == $::NS(muc#user)} {
		    process_muc_user $xlib $from $type $subels
		}
	    }
	}
    }
}

hook::add client_presence_hook muc::process_presence

###############################################################################

proc muc::process_muc_user {xlib user type children} {
    variable users
    variable options

    foreach child $children {
	::xmpp::xml::split $child tag xmlns attrs cdata subels
	switch -- $tag {
	    item {
		if {$type != "unavailable"} {
		    set users(jid,$xlib,$user) [::xmpp::xml::getAttr $attrs jid]
		    track_room_position $xlib $user \
			[::xmpp::xml::getAttr $attrs role] \
			[::xmpp::xml::getAttr $attrs affiliation]
		} else {
		    set new_nick [::xmpp::xml::getAttr $attrs nick]
		    foreach ch $subels {
			::xmpp::xml::split $ch stag sxmlns sattrs scdata ssubels
			switch -- $stag {
			    reason {
				set reason $scdata
			    }
			    actor {
				set actor [::xmpp::xml::getAttr $sattrs jid]
			    }
			}
		    }
		}
	    }
	    destroy {
		set group [::xmpp::jid::stripResource $user]
		set chatid [chat::chatid $xlib $group]
		set msg [::msgcat::mc "Room is destroyed"]
		foreach ch $subels {
		    ::xmpp::xml::split $ch stag sxmlns sattrs scdata ssubels
		    if {$stag == "reason" && [string trim $scdata] != ""} {
			append msg [::msgcat::mc "\nReason: %s" [string trim $scdata]]
		    }
		}
		set altjid [::xmpp::xml::getAttr $attrs jid]
		if {$altjid != ""} {
		    append msg [::msgcat::mc "\nAlternative venue: %s" $altjid]
		}
		if {$options(gen_enter_exit_msgs)} {
		    chat::add_message \
			$chatid $group groupchat $msg {}
		}
	    }
	    status {
		set code [::xmpp::xml::getAttr $attrs code]
		set group [::xmpp::jid::stripResource $user]
		set chatid [chat::chatid $xlib $group]
		switch -- $code {
		    201 {
			chat::add_message \
			    $chatid $group groupchat \
			    [::msgcat::mc "A new room is created"] {}
			# 201: room creation
			if {$options(propose_configure)} {
			    request_config_dialog $chatid
			} else {
			    # requesting "instant" room as specified in XEP-0045
			    # if the user wants to configure room (s)he can do it later
			    request_instant_room $chatid
			}
		    }
		    301 -
		    307 -
		    321 -
		    322 {
			# 301: ban, 307: kick, 321: loosing membership
			# 322: room becomes members-only
			set nick [chat::get_nick $xlib $user groupchat]
			set real_jid [get_real_jid $xlib $group/$nick]
			if {$real_jid != ""} {
			    set real_jid " ($real_jid)"
			}
			switch -- $code {
			    301 {set action \
				     [::msgcat::mc "%s has been banned" \
					  $nick$real_jid]}
			    307 {set action \
				     [::msgcat::mc "%s has been kicked" \
					  $nick$real_jid]}
			    321 {set action \
				     [::msgcat::mc \
					  "%s has been kicked because\
					   of membership loss" \
					  $nick$real_jid]}
			    322 {set action \
				     [::msgcat::mc \
					  "%s has been kicked because\
					   room became members-only" \
					  $nick$real_jid]}
			}

			if {[info exists actor] && $actor != ""} {
			    append action [::msgcat::mc " by %s" $actor]
			}

			if {[info exists reason] && $reason != ""} {
			    append action ": $reason"
			}

			if {$options(gen_enter_exit_msgs)} {
			    variable ignore_unavailable $nick
			    chat::add_message \
				$chatid $group groupchat $action {}
			}
		    }
		    303 {
			# 303: nickname change
			set nick [chat::get_nick $xlib $user groupchat]
			if {[info exists new_nick] && $new_nick != ""} {
			    if {$nick == [get_our_groupchat_nick $chatid]} {
				set_our_groupchat_nick $chatid $new_nick
			    }
			    # TODO may be this reporting should not be done
			    # if the $nick is being ignored (MUC ignore)
			    if {$options(gen_enter_exit_msgs)} {
				variable ignore_available   $new_nick
				variable ignore_unavailable $nick

				set real_jid [get_real_jid $xlib $group/$nick]
				if {$real_jid != ""} {
				    set real_jid " ($real_jid)"
				}
				chat::add_message \
				    $chatid $group groupchat \
				    [::msgcat::mc "%s is now known as %s" \
					 $nick$real_jid $new_nick] {}
			    }
			    ::hook::run room_nickname_changed_hook \
					$xlib $group $nick $new_nick
			}
		    }
		}
	    }
	}
    }
}


proc muc::process_available {chatid nick} {
    variable pending_nicks

    set xlib [chat::get_xlib $chatid]
    set group [chat::get_jid $chatid]

    if {![is_compatible $group] && [is_changing_nick $chatid] && \
	    [string equal $pending_nicks($chatid) $nick]} {
	set_our_groupchat_nick $chatid $nick
	unset pending_nicks($chatid)
    }
}


proc muc::process_unavailable {chatid nick} {
    variable options
    variable ignore_unavailable

    set group [chat::get_jid $chatid]

    if {[is_compatible $group] && $options(gen_enter_exit_msgs) && \
	    (![info exists ignore_unavailable] || \
		 $ignore_unavailable != $nick)} {
	set xlib [chat::get_xlib $chatid]
	set error [get_jid_presence_info error $xlib $group/$nick]
	if {$error != ""} {
	    set status [::xmpp::stanzaerror::message $error]
	} else {
	    set status [get_jid_presence_info status $xlib $group/$nick]
	}
	if {$status != ""} {
	    set end ": $status"
	} else {
	    set end ""
	}
	chat::add_message $chatid $group groupchat \
	    [cconcat [::msgcat::mc "%s has left" $nick] $end] {}
    }
    catch { unset ignore_unavailable }
}

proc muc::report_available {chatid nick entered} {
    variable options
    variable users
    variable ignore_available

    set xlib [::chat::get_xlib $chatid]
    set group [::chat::get_jid $chatid]

    if {![is_compatible $group]} return

    set jid $group/$nick

    set msg ""

    set report_entered [expr {$entered && $options(gen_enter_exit_msgs) \
	&& (![info exists ignore_available] || $ignore_available != $nick)}]

    if {$report_entered} {
	set real_jid [get_real_jid $xlib $jid]
	if {$real_jid != ""} {
	    set occupant "$nick ($real_jid)"
	} else {
	    set occupant $nick
	}
	set msg [::msgcat::mc "%s has entered" $occupant]
	if {$options(gen_muc_position_change_msgs)} {
	    append msg " " [::msgcat::mc "as %s/%s" \
		[::msgcat::mc $users(affiliation,$xlib,$jid)] \
		[::msgcat::mc $users(role,$xlib,$jid)]]
	}
    }
    catch { unset ignore_available }

    if {$options(gen_muc_status_change_msgs)} {
	set status [::get_user_status $xlib $jid]
	if {$report_entered} {
	    append msg " " [::msgcat::mc "and"] " "
	} else {
	    append msg $nick " "
	}
	append msg [::get_long_status_desc $status]
	set desc [::get_user_status_desc $xlib $jid]
	if {$desc != {}} {
	    append msg " ($desc)"
	}
    }

    ::chat::add_message $chatid $group groupchat $msg {}
}

proc muc::track_room_position {xlib jid role affiliation} {
    variable options
    variable users

    upvar 0 users(role,$xlib,$jid) _role \
	users(affiliation,$xlib,$jid) _aff

    set group  [::xmpp::jid::stripResource $jid]
    if {![is_compatible $group]} return

    set chatid [chat::chatid $xlib $group]

    if {[chat::is_opened $chatid] && $options(gen_muc_position_change_msgs)} {
	set nick [chat::get_nick $xlib $jid groupchat]

	set role_changed [expr {![info exists _role] \
	    || ![string equal $role $_role]}]
	set aff_changed  [expr {![info exists _aff] \
	    || ![string equal $affiliation $_aff]}]

	if {$aff_changed} {
	    if {$role_changed} {
		set msg [::msgcat::mc "%s has been assigned a new room position: %s/%s" \
		    $nick [::msgcat::mc $affiliation] [::msgcat::mc $role]]
	    } else {
		set msg [::msgcat::mc "%s has been assigned a new affiliation: %s" \
		    $nick [::msgcat::mc $affiliation]]
	    }
	} elseif {$role_changed} {
	    set msg [::msgcat::mc "%s has been assigned a new role: %s" \
		$nick [::msgcat::mc $role]]
	} else {
	    set msg ""
	}

	if {$msg != ""} {
	    ::chat::add_message $chatid $group groupchat $msg {}
	}
    }

    set _aff  $affiliation
    set _role $role
}

###############################################################################

proc muc::change_nick {chatid nick} {
    global userstatus textstatus
    global statusdesc
    variable pids

    set group [chat::get_jid $chatid]
    set xlib [chat::get_xlib $chatid]

    if {![is_compatible $group]} {
	variable pending_nicks
	set pending_nicks($chatid) $nick
    }

    if {$userstatus == "invisible"} {
	set status available
    } else {
	set status $userstatus
    }

    if {$textstatus == ""} {
	set tstatus $statusdesc($status)
    } else {
	set tstatus $textstatus
    }

    set id [::xmpp::packetID $xlib]
    set pids($xlib,$id) $chatid

    send_custom_presence $xlib $group/$nick $status \
			 -status $tstatus \
			 -id $id
}


proc muc::process_change_nick {xlib from type x args} {
    variable pids

    set status ""
    set error [::msgcat::mc "Error"]
    foreach {key val} $args {
	switch -- $key {
	    -id     {set id $val}
	    -status {set status $val}
	    -error  {set error [::xmpp::stanzaerror::message $val]}
	}
    }

    if {![info exists id] || ![info exists pids($xlib,$id)]} {
	return
    }

    set chatid $pids($xlib,$id)
    unset pids($xlib,$id)

    if {![string equal $type error]} {
	return
    }

    if {![is_compatible [chat::get_jid $chatid]] && \
	    [is_changing_nick $chatid]} {
	variable pending_nicks
	unset pending_nicks($chatid)
    }

    chat::add_message $chatid [::xmpp::jid::stripResource $from] error $error {}
    return stop
}

hook::add client_presence_hook muc::process_change_nick 10

proc muc::is_changing_nick {chatid} {
    variable pending_nicks

    if {[info exists pending_nicks($chatid)]} {
	return 1
    } else {
	return 0
    }
}

proc muc::clear_changing_nicks {xlib} {
    variable pending_nicks
    variable pids

    array unset pending_nicks [list $xlib *]
    array unset pids $xlib,*
}

hook::add disconnected_hook muc::clear_changing_nicks

###############################################################################

proc muc::request_negotiation {xlib group} {
    variable muc_compatible

    # It's almost impossible to find MUC-incompatible room now, so the default
    # value is 1
    set muc_compatible($group) 1

    disco::request_info $xlib [::xmpp::jid::server $group] \
	-cache yes \
	-command [list muc::recv_negotiation1 $xlib $group]
}

proc muc::recv_negotiation1 {xlib group res identities features extras} {
    variable muc_compatible

    if {[string equal $res ok] && [lsearch -exact $features $::NS(muc)] >= 0} {
	set muc_compatible($group) 1
	set muc_compatible([::xmpp::jid::server $group]) 1
	return
    }

    disco::request_info $xlib $group \
	-cache yes \
	-command [list muc::recv_negotiation2 $group]
}

proc muc::recv_negotiation2 {group res identities features extras} {
    variable muc_compatible

    if {[string equal $res ok] && [lsearch -exact $features $::NS(muc)] >= 0} {
	set muc_compatible($group) 1
	return
    }

    set muc_compatible($group) 0
}

proc muc::is_compatible {group} {
    variable muc_compatible

    if {[info exists muc_compatible($group)]} {
	return $muc_compatible($group)
    } elseif {[info exists muc_compatible([::xmpp::jid::server $group])]} {
	return $muc_compatible([::xmpp::jid::server $group])
    } else {
	return 0
    }
}

###############################################################################

proc muc::add_user_popup_info {infovar xlib user} {
    variable users
    upvar 0 $infovar info

    if {[info exists users(jid,$xlib,$user)] && \
	    $users(jid,$xlib,$user) != ""} {
	append info [::msgcat::mc "\n\tJID: %s" $users(jid,$xlib,$user)]
    }
    if {[info exists users(affiliation,$xlib,$user)]} {
	append info [::msgcat::mc "\n\tAffiliation: %s" \
			 $users(affiliation,$xlib,$user)]
    }
}

hook::add roster_user_popup_info_hook muc::add_user_popup_info

###############################################################################

proc muc::set_message_timestamp {chatid from type body x} {
    variable timestamps

    if {![chat::is_disconnected $chatid]} {
	set timestamps($chatid) [clock seconds]
    }
}

#hook::add draw_message_hook muc::set_message_timestamp 15

proc muc::clear_message_timestamp {chatid} {
    variable timestamps

    catch { unset timestamps($chatid) }
}

#hook::add close_chat_post_hook muc::clear_message_timestamp

###############################################################################

proc muc::join_group {xlib group nick {password ""}} {
    global userstatus textstatus
    global statusdesc
    variable options
    variable timestamps
    variable muc_password

    set group [::xmpp::jid::normalize $group]
    set chatid [chat::chatid $xlib $group]

    if {[info exists chat::chats(status,$chatid)] && \
	    ![string equal $chat::chats(status,$chatid) disconnected]} {
	change_nick $chatid $nick
	return
    }

    #privacy::add_to_special_list $xlib conference [::xmpp::jid::server $group]

    set_our_groupchat_nick $chatid $nick

    #chat::open_window $chatid groupchat
    #update idletasks

    #request_negotiation $xlib $group

    set x_subtags {}

    lappend x_subtags [::xmpp::xml::create password -cdata $password]
    set muc_password($chatid) $password

    set history_vars {}
    if {$options(history_maxchars) >= 0} {
	lappend history_vars maxchars $options(history_maxchars)
    }
    if {$options(history_maxstanzas) >= 0} {
	lappend history_vars maxstanzas $options(history_maxstanzas)
    }
    if {$options(request_only_unseen_history) && \
	    [info exists timestamps($chatid)]} {
	lappend history_vars \
		seconds [expr {[clock seconds] - $timestamps($chatid) + 2}]
    }
    if {![lempty $history_vars]} {
	lappend x_subtags [::xmpp::xml::create history -attrs $history_vars]
    }

    if {$userstatus == "invisible"} {
	set status available
    } else {
	set status $userstatus
    }

    if {$textstatus == ""} {
	set tstatus $statusdesc($status)
    } else {
	set tstatus $textstatus
    }

    send_presence $xlib $status \
		  -to $group/$nick \
		  -status $tstatus \
		  -xlist [list [::xmpp::xml::create x \
				    -xmlns $::NS(muc) \
				    -subelements $x_subtags]]
}

###############################################################################

proc muc::leave_group {xlib group nick status} {
    send_presence $xlib unavailable \
		  -to $group/$nick \
		  -status $status
}

###############################################################################

proc muc::invite_muc {xlib group jid reason} {
    # If $jid is a 'real' JID then invite
    # If $jid is in a conference room try to invite real JID

    set real_jid [get_real_jid $xlib $jid]
    if {$real_jid == ""} {
	set real_jid $jid
    }

    message::send_msg $xlib $group \
	-xlist [list \
	    [::xmpp::xml::create x \
		 -xmlns $::NS(muc#user) \
		 -subelement [::xmpp::xml::create invite \
				    -attrs [list to $real_jid] \
				    -subelement [::xmpp::xml::create reason \
							-cdata $reason]]]]
}

proc muc::invite_xconference {xlib group jid reason} {
    message::send_msg $xlib $jid \
	-type normal \
	-subject "Invitation" \
	-body $reason \
	-xlist [list [::xmpp::xml::create x \
			    -xmlns $::NS(xconference) \
			    -attrs [list jid $group]]]
}

###############################################################################

proc muc::process_invitation {rowvar bodyvar f x xlib from id type replyP} {
    upvar 2 $rowvar row
    upvar 2 $bodyvar body

    foreach xa $x {
	::xmpp::xml::split $xa tag xmlns attrs cdata subels

	switch -- $xmlns \
	    $::NS(xconference) {
		set xconference_group [::xmpp::xml::getAttr $attrs jid]
		set xconference_password ""
		if {[string equal $body ""] && ![string equal $cdata ""]} {
		    set xconference_body $cdata
		} else {
		    set xconference_body $body
		}
	    } \
	    $::NS(muc#user) {
		set password ""
		set inviter ""
		foreach ch $subels {
		    ::xmpp::xml::split $ch stag sxmlns sattrs scdata ssubels

		    switch -- $stag {
			invite {
			    set inviter [::xmpp::xml::getAttr $sattrs from]
			    if {![string equal $inviter ""]} {
				foreach c [connections] {
				    set name \
					[roster::itemconfig $c \
					     [roster::find_jid $c $inviter] \
						  -name]
				    if {$name != ""} break
				}
				if {![cequal $name ""]} {
				    set inviter "$name ($inviter)"
				}
				set muc_body \
				    [::msgcat::mc \
					 "%s invites you to conference\
					  room %s" \
					 $inviter $from]

				foreach sch $ssubels {
				    ::xmpp::xml::split $sch sstag ssxmlns ssattrs \
							    sscdata sssubels
				    if {[string equal $sstag "reason"]} {
					append muc_body \
					    [::msgcat::mc "\nReason is: %s" \
						 $sscdata]
				    }
				}
			    }
			    # TODO decline
			}
			password {
			    set password $scdata
			}
		    }
		}
		if {![string equal $inviter ""]} {
		    set muc_group $from
		    set muc_password $password
		}
	    }
    }

    if {[info exists muc_group] && $muc_group != ""} {
	process_x_conference $f $xlib $muc_group $muc_password $row
	incr row
	set body $muc_body
	return
    } elseif {[info exists xconference_group] && $xconference_group != ""} {
	process_x_conference $f $xlib $xconference_group \
	    $xconference_password $row
	incr row
	set body $xconference_body
	return
    }
    
    return
}

hook::add message_process_x_hook muc::process_invitation

proc muc::process_x_conference {f xlib group password row} {
    global gr_nick

    label $f.lgroup$row -text [::msgcat::mc "Invited to:"]
    button $f.group$row -text $group \
	-command [list ::join_group $xlib $group \
		       -nick [get_group_nick $group $gr_nick] \
		       -password $password]
    
    grid $f.lgroup$row -row $row -column 0 -sticky e
    grid $f.group$row  -row $row -column 1 -sticky ew
}

###############################################################################

proc muc::join {xlib jid args} {
    global gr_nick

    set category conference
    foreach {opt val} $args {
	switch -- $opt {
	    -category { set category $val }
	}
    }

    if {![cequal $category conference]} {
	return
    }

    if {![cequal [::xmpp::jid::node $jid] {}]} {
	::join_group $xlib $jid -nick [get_group_nick $jid $gr_nick]
    } else {
#::join_group_dialog $xlib -server [::xmpp::jid::server $jid] -group {}
    }
}

#hook::add postload_hook \
    [list disco::browser::register_feature_handler jabber:iq:conference \
	 muc::join -desc [list conference [::msgcat::mc "Join conference"]]]
#hook::add postload_hook \
    [list disco::browser::register_feature_handler $::NS(muc) muc::join \
	 -desc [list conference [::msgcat::mc "Join conference"]]]
#hook::add postload_hook \
    [list disco::browser::register_feature_handler "gc-1.0" muc::join \
	 -desc [list conference [::msgcat::mc "Join groupchat"]]]

###############################################################################


proc muc::disco_reply {type xlib from lang} {
    variable options

    if {!$options(report_muc_rooms)} {
	return {error cancel not-allowed}
    }

    switch -- $type {
	info {
	    return [list result {}]
	}
	items {
	    set res {}
	    foreach chatid [lfilter chat::is_groupchat [chat::opened $xlib]] {
		set group [chat::get_jid $chatid]
		if {[is_compatible $group]} {
		    lappend res [list jid $group]
		}
	    }
	    return [list result $res]
	}
    }
}

hook::add postload_hook \
    [list disco::register_node $::NS(muc#rooms) muc::disco_reply \
	  [::trans::trans "Current rooms"]]

###############################################################################

# vim:ts=8:sw=4:sts=4:noet
